msg_tool\scripts\musica\archive/
paz.rs

1use crate::ext::io::*;
2use crate::scripts::base::*;
3use crate::types::*;
4use crate::utils::blowfish::*;
5use crate::utils::encoding::*;
6use crate::utils::rc4::*;
7use crate::utils::serde_base64bytes::Base64Bytes;
8use crate::utils::struct_pack::*;
9use crate::utils::xored_stream::*;
10use anyhow::Result;
11use flate2::read::ZlibDecoder;
12use flate2::write::ZlibEncoder;
13use msg_tool_macro::{StructPack, StructUnpack};
14use serde::Deserialize;
15use std::collections::{BTreeMap, HashMap};
16use std::io::{Read, Seek, SeekFrom, Write};
17use std::sync::{Arc, Mutex};
18
19include_flate::flate!(static PAZ_DATA: str from "src/scripts/musica/archive/paz.json" with zstd);
20
21#[derive(Clone, Debug, Deserialize)]
22#[serde(rename_all = "PascalCase")]
23struct ArcKey {
24    index_key: Base64Bytes,
25    data_key: Option<Base64Bytes>,
26}
27
28#[derive(Clone, Debug, Deserialize)]
29#[serde(rename_all = "PascalCase")]
30struct Schema {
31    version: u32,
32    arc_keys: HashMap<String, ArcKey>,
33    type_keys: HashMap<String, String>,
34    /// PAZ file signature
35    signature: u32,
36    xor_key: u8,
37    title: Option<String>,
38}
39
40impl Schema {
41    pub fn get_type_key(&self, entry: &PazEntry, is_audio: bool) -> Option<&str> {
42        if is_audio {
43            return self.type_keys.get("ogg").map(|s| s.as_str());
44        }
45        let mut name = std::path::Path::new(&entry.name)
46            .extension()?
47            .to_string_lossy()
48            .to_lowercase();
49        if name == "mpg" || name == "mpeg" {
50            name = "avi".to_string();
51        }
52        self.type_keys.get(&name).map(|s| s.as_str())
53    }
54}
55
56lazy_static::lazy_static! {
57    static ref PAZ_SCHEMA: BTreeMap<String, Schema> = {
58        serde_json::from_str(&PAZ_DATA).expect("Failed to parse paz.json")
59    };
60    static ref ALIAS_TABLE: HashMap<String, String> = {
61        let mut table = HashMap::new();
62        for (game, fulltitle) in get_supported_games_with_title() {
63            if let Some(title) = fulltitle {
64                let mut alias_count = 0usize;
65                for part in title.split("|") {
66                    let alias = part.trim();
67                    table.insert(alias.to_string(), game.to_string());
68                    alias_count += 1;
69                }
70                // also insert full title if there are multiple aliases
71                if alias_count > 1 {
72                    table.insert(title.to_string(), game.to_string());
73                }
74            }
75        }
76        table
77    };
78}
79
80/// Get the supported game titles for PAZ archives.
81pub fn get_supported_games() -> Vec<&'static str> {
82    PAZ_SCHEMA.keys().map(|s| s.as_str()).collect()
83}
84
85/// Get the supported game titles for PAZ archives with their full titles.
86pub fn get_supported_games_with_title() -> Vec<(&'static str, Option<&'static str>)> {
87    PAZ_SCHEMA
88        .iter()
89        .map(|(k, v)| (k.as_str(), v.title.as_deref()))
90        .collect()
91}
92
93fn query_paz_schema(game: &str) -> Option<&'static Schema> {
94    PAZ_SCHEMA.get(game).or_else(|| {
95        ALIAS_TABLE
96            .get(game)
97            .and_then(|real_game| PAZ_SCHEMA.get(real_game))
98    })
99}
100
101fn query_paz_schema_by_signature(signature: u32) -> Option<(&'static str, &'static Schema)> {
102    for (game, schema) in PAZ_SCHEMA.iter() {
103        if schema.signature == signature {
104            return Some((game.as_str(), schema));
105        }
106    }
107    None
108}
109
110#[derive(Debug)]
111pub struct PazArcBuilder {}
112
113impl PazArcBuilder {
114    pub fn new() -> Self {
115        PazArcBuilder {}
116    }
117}
118
119impl ScriptBuilder for PazArcBuilder {
120    fn default_encoding(&self) -> Encoding {
121        Encoding::Cp932
122    }
123
124    fn default_archive_encoding(&self) -> Option<Encoding> {
125        Some(Encoding::Cp932)
126    }
127
128    fn build_script(
129        &self,
130        buf: Vec<u8>,
131        filename: &str,
132        _encoding: Encoding,
133        archive_encoding: Encoding,
134        config: &ExtraConfig,
135        _archive: Option<&Box<dyn Script>>,
136    ) -> Result<Box<dyn Script + Send + Sync>> {
137        Ok(Box::new(PazArc::new(
138            MemReader::new(buf),
139            filename,
140            archive_encoding,
141            config,
142        )?))
143    }
144
145    fn build_script_from_file(
146        &self,
147        filename: &str,
148        _encoding: Encoding,
149        archive_encoding: Encoding,
150        config: &ExtraConfig,
151        _archive: Option<&Box<dyn Script>>,
152    ) -> Result<Box<dyn Script + Send + Sync>> {
153        let f = std::fs::File::open(filename)?;
154        let f = std::io::BufReader::new(f);
155        Ok(Box::new(PazArc::new(
156            f,
157            filename,
158            archive_encoding,
159            config,
160        )?))
161    }
162
163    fn build_script_from_reader<'a>(
164        &self,
165        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
166        filename: &str,
167        _encoding: Encoding,
168        archive_encoding: Encoding,
169        config: &ExtraConfig,
170        _archive: Option<&Box<dyn Script>>,
171    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
172        Ok(Box::new(PazArc::new(
173            reader,
174            filename,
175            archive_encoding,
176            config,
177        )?))
178    }
179
180    fn extensions(&self) -> &'static [&'static str] {
181        &["paz"]
182    }
183
184    fn is_archive(&self) -> bool {
185        true
186    }
187
188    fn script_type(&self) -> &'static ScriptType {
189        &ScriptType::MusicaPaz
190    }
191
192    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
193        if buf_len >= 4 {
194            let sign = u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]);
195            if let Some(_) = query_paz_schema_by_signature(sign) {
196                return Some(10);
197            }
198        }
199        None
200    }
201
202    fn create_archive(
203        &self,
204        filename: &str,
205        files: &[&str],
206        encoding: Encoding,
207        config: &ExtraConfig,
208    ) -> Result<Box<dyn Archive>> {
209        let file = std::fs::File::create(filename)?;
210        let file = std::io::BufWriter::new(file);
211        Ok(Box::new(PazArcWriter::new(
212            file, files, encoding, filename, config,
213        )?))
214    }
215}
216
217#[derive(Debug, StructPack, StructUnpack, Clone)]
218struct PazEntry {
219    #[cstring]
220    name: String,
221    offset: u64,
222    unpacked_size: u32,
223    size: u32,
224    aligned_size: u32,
225    flags: u32,
226}
227
228impl PazEntry {
229    pub fn is_compressed(&self) -> bool {
230        (self.flags & 0x1) != 0
231    }
232
233    pub fn set_is_compressed(&mut self, compressed: bool) {
234        if compressed {
235            self.flags |= 0x1;
236        } else {
237            self.flags &= !0x1;
238        }
239    }
240}
241
242#[derive(Debug)]
243pub struct PazArc<'a> {
244    stream: Arc<Mutex<MultipleReadStream<'a>>>,
245    schema: Schema,
246    arc_key: ArcKey,
247    entries: Vec<PazEntry>,
248    archive_encoding: Encoding,
249    xor_key: u8,
250    is_audio: bool,
251    mov_key: Option<Vec<u8>>,
252}
253
254const AUDIO_PAZ_NAMES: &[&str] = &["bgm", "se", "voice", "pmbgm", "pmse", "pmvoice"];
255
256impl<'a> PazArc<'a> {
257    pub fn new<T: ReadSeek + Send + Sync + 'a>(
258        reader: T,
259        filename: &str,
260        archive_encoding: Encoding,
261        config: &ExtraConfig,
262    ) -> Result<Self> {
263        let mut stream = MultipleReadStream::new();
264        stream.add_stream(reader)?;
265        for suffix in b'A'..=b'Z' {
266            let arc_filename = format!("{}{}", filename, suffix as char);
267            if let Ok(f) = std::fs::File::open(&arc_filename) {
268                let f = std::io::BufReader::new(f);
269                stream.add_stream_boxed(Box::new(f))?;
270            } else {
271                break;
272            }
273        }
274        let arc_size = stream.stream_length()?;
275        let schema = if let Some(title) = &config.musica_game_title {
276            let schema = query_paz_schema(title).ok_or_else(|| {
277                anyhow::anyhow!("Unsupported game title '{}' for PAZ archive", title)
278            })?;
279            let sig = stream.read_u32()?;
280            if schema.signature != 0 && schema.signature != sig {
281                let extra_title = if let Some(title) = &schema.title {
282                    format!(" ('{}')", title)
283                } else {
284                    "".to_string()
285                };
286                eprintln!(
287                    "Warning: PAZ signature {:08X} does not match expected signature {:08X} for game '{}'{}",
288                    sig, schema.signature, title, extra_title
289                );
290                crate::COUNTER.inc_warning();
291            }
292            schema
293        } else {
294            let sig = stream.read_u32()?;
295            let (game, schema) = query_paz_schema_by_signature(sig).ok_or_else(|| {
296                anyhow::anyhow!(
297                    "Unknown PAZ signature {:08X}. Use --musica-game-title to specify game title.",
298                    sig
299                )
300            })?;
301            eprintln!("Detected PAZ archive for game '{}'", game);
302            schema
303        };
304        let arc_name = std::path::Path::new(filename)
305            .file_stem()
306            .and_then(|s| s.to_str())
307            .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?
308            .to_lowercase();
309        let is_audio = AUDIO_PAZ_NAMES.contains(&arc_name.as_str());
310        let is_video = arc_name == "mov";
311        let arc_key = schema.arc_keys.get(&arc_name).ok_or_else(|| {
312            anyhow::anyhow!(
313                "No ARC key found for archive name '{}' in game schema",
314                arc_name
315            )
316        })?;
317        let mut start_offset = if schema.version > 0 { 0x20 } else { 0 };
318        stream.seek(SeekFrom::Start(start_offset))?;
319        let mut index_size = stream.read_u32()?;
320        start_offset += 4;
321        let xor_key = if let Some(xor_key) = config.musica_xor_key {
322            xor_key
323        } else if schema.xor_key != 0 {
324            schema.xor_key
325        } else {
326            let xor = (index_size >> 24) as u8;
327            eprintln!("Detected xor key from index size: {}", xor);
328            xor
329        };
330        if xor_key != 0 {
331            let t = xor_key as u32;
332            index_size ^= t << 24 | t << 16 | t << 8 | t;
333        }
334        if index_size & 7 != 0 || index_size as u64 > arc_size - start_offset {
335            return Err(anyhow::anyhow!("Invalid PAZ index size: {}", index_size));
336        }
337        let mut mov_key = None;
338        let entries = {
339            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&arc_key.index_key)?;
340            let mut index_stream: Box<dyn ReadSeek> = Box::new(StreamRegion::new(
341                &mut stream,
342                start_offset,
343                start_offset + index_size as u64,
344            )?);
345            if xor_key != 0 {
346                index_stream = Box::new(XoredStream::new(index_stream, xor_key));
347            }
348            let mut index_stream = BlowfishDecryptor::new(blowfish.clone(), index_stream);
349            let count = index_stream.read_u32()?;
350            if is_video {
351                let mut key = index_stream.read_exact_vec(0x100)?;
352                if schema.version < 1 {
353                    let mut nkey = vec![0u8; 0x100];
354                    for i in 0..0x100 {
355                        nkey[key[i] as usize] = i as u8;
356                    }
357                    key = nkey;
358                }
359                mov_key = Some(key);
360            }
361            // Each PAZ entry at least needs 0x18 bytes
362            let least_len = match count.checked_mul(0x18) {
363                Some(v) => v,
364                None => {
365                    return Err(anyhow::anyhow!("Invalid PAZ entry count: {}", count));
366                }
367            };
368            let other_len = if is_video { 0x104 } else { 4 };
369            if least_len > index_size - other_len {
370                return Err(anyhow::anyhow!("Invalid PAZ entry count: {}", count));
371            }
372            let mut entries = Vec::with_capacity(count as usize);
373            for _ in 0..count {
374                let entry: PazEntry = index_stream.read_struct(false, archive_encoding, &None)?;
375                entries.push(entry);
376            }
377            entries
378        };
379        Ok(PazArc {
380            stream: Arc::new(Mutex::new(stream)),
381            schema: schema.clone(),
382            arc_key: arc_key.clone(),
383            entries,
384            archive_encoding,
385            xor_key,
386            is_audio,
387            mov_key,
388        })
389    }
390}
391
392impl<'b> Script for PazArc<'b> {
393    fn default_output_script_type(&self) -> OutputScriptType {
394        OutputScriptType::Json
395    }
396
397    fn default_format_type(&self) -> FormatOptions {
398        FormatOptions::None
399    }
400
401    fn is_archive(&self) -> bool {
402        true
403    }
404
405    fn iter_archive_filename<'a>(
406        &'a self,
407    ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
408        Ok(Box::new(
409            self.entries.iter().map(|entry| Ok(entry.name.clone())),
410        ))
411    }
412
413    fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
414        Ok(Box::new(self.entries.iter().map(|entry| Ok(entry.offset))))
415    }
416
417    fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
418        if index >= self.entries.len() {
419            return Err(anyhow::anyhow!("Index out of bounds"));
420        }
421        let entry = self.entries[index].clone();
422        let stream = XoredStream::new(
423            StreamRegion::new(
424                MutexWrapper::new(self.stream.clone(), entry.offset),
425                entry.offset,
426                entry.offset + entry.aligned_size as u64,
427            )?,
428            self.xor_key,
429        );
430        if let Some(data_key) = &self.arc_key.data_key {
431            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&data_key.bytes)?;
432            let stream = StreamRegion::new(
433                BlowfishDecryptor::new(blowfish, stream),
434                0,
435                entry.size as u64,
436            )?;
437            if self.schema.version > 0 && !entry.is_compressed() {
438                if let Some(type_key) = self.schema.get_type_key(&entry, self.is_audio) {
439                    let key = format!(
440                        "{} {:08X} {}",
441                        entry.name.to_ascii_lowercase(),
442                        entry.unpacked_size,
443                        type_key
444                    );
445                    let key = encode_string(self.archive_encoding, &key, false)?;
446                    let mut rc4 = Rc4::new(&key);
447                    if self.schema.version >= 2 {
448                        let crc = crc32fast::hash(&key);
449                        let skip = ((crc >> 12) as i32) & 0xFF;
450                        rc4.skip_bytes(skip as usize);
451                    }
452                    let stream = Rc4Stream::new(stream, rc4);
453                    return Ok(Box::new(PazFileEntry::new(entry, stream)));
454                }
455            }
456            if entry.is_compressed() {
457                let stream = ZlibDecoder::new(stream);
458                return Ok(Box::new(PazFileEntry::new(entry, stream)));
459            }
460            return Ok(Box::new(PazFileEntry::new(entry, stream)));
461        } else if let Some(mov_key) = &self.mov_key {
462            if self.schema.version < 1 {
463                let stream = TableEncryptedStream::new(stream, mov_key.clone())?;
464                if entry.is_compressed() {
465                    let stream = ZlibDecoder::new(stream);
466                    return Ok(Box::new(PazFileEntry::new(entry, stream)));
467                }
468                return Ok(Box::new(PazFileEntry::new(entry, stream)));
469            }
470            let type_key = self
471                .schema
472                .get_type_key(&entry, self.is_audio)
473                .ok_or_else(|| {
474                    anyhow::anyhow!("Data decryption key not found for entry '{}'.", entry.name)
475                })?;
476            let key = format!(
477                "{} {:08X} {}",
478                entry.name.to_ascii_lowercase(),
479                entry.unpacked_size,
480                type_key
481            );
482            let key = encode_string(self.archive_encoding, &key, false)?;
483            let mut rkey = mov_key.clone();
484            let key_len = key.len();
485            for i in 0..0x100 {
486                rkey[i] ^= key[i % key_len];
487            }
488            let mut rc4 = Rc4::new(&rkey);
489            let key_block = rc4.generate_block((entry.size as usize).min(0x10000));
490            let stream = XoredKeyStream::new(stream, key_block, 0);
491            if entry.is_compressed() {
492                let stream = ZlibDecoder::new(stream);
493                return Ok(Box::new(PazFileEntry::new(entry, stream)));
494            }
495            return Ok(Box::new(PazFileEntry::new(entry, stream)));
496        }
497        Err(anyhow::anyhow!("Data decryption key not found."))
498    }
499}
500
501#[derive(Debug)]
502struct PazFileEntry<T: Read> {
503    entry: PazEntry,
504    stream: T,
505}
506
507impl<T: Read> PazFileEntry<T> {
508    pub fn new(entry: PazEntry, stream: T) -> Self {
509        PazFileEntry { entry, stream }
510    }
511}
512
513impl<T: Read> Read for PazFileEntry<T> {
514    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
515        self.stream.read(buf)
516    }
517}
518
519impl<T: Seek + Read> Seek for PazFileEntry<T> {
520    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
521        self.stream.seek(pos)
522    }
523
524    fn rewind(&mut self) -> std::io::Result<()> {
525        self.stream.rewind()
526    }
527
528    fn stream_position(&mut self) -> std::io::Result<u64> {
529        self.stream.stream_position()
530    }
531}
532
533impl<T: Read> ArchiveContent for PazFileEntry<T> {
534    fn name(&self) -> &str {
535        &self.entry.name
536    }
537
538    fn size(&self) -> Option<u64> {
539        Some(self.entry.size as u64)
540    }
541
542    fn script_type(&self) -> Option<&ScriptType> {
543        let ext_name = std::path::Path::new(&self.entry.name)
544            .extension()
545            .and_then(|s| s.to_str())
546            .unwrap_or("")
547            .to_lowercase();
548        match ext_name.as_str() {
549            "sc" => Some(&ScriptType::Musica),
550            _ => None,
551        }
552    }
553}
554
555struct TableEncryptedStream<T> {
556    inner: T,
557    table: Vec<u8>,
558}
559
560impl<T> TableEncryptedStream<T> {
561    pub fn new(inner: T, table: Vec<u8>) -> Result<Self> {
562        if table.len() != 256 {
563            return Err(anyhow::anyhow!(
564                "Table length must be 256, got {}",
565                table.len()
566            ));
567        }
568        Ok(TableEncryptedStream { inner, table })
569    }
570}
571
572impl<T: Read> Read for TableEncryptedStream<T> {
573    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
574        let readed = self.inner.read(buf)?;
575        for i in 0..readed {
576            buf[i] = self.table[buf[i] as usize];
577        }
578        Ok(readed)
579    }
580}
581
582impl<T: Seek> Seek for TableEncryptedStream<T> {
583    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
584        self.inner.seek(pos)
585    }
586
587    fn rewind(&mut self) -> std::io::Result<()> {
588        self.inner.rewind()
589    }
590
591    fn stream_position(&mut self) -> std::io::Result<u64> {
592        self.inner.stream_position()
593    }
594}
595
596impl<T: Write> Write for TableEncryptedStream<T> {
597    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
598        let mut encrypted_buf = vec![0u8; buf.len()];
599        for i in 0..buf.len() {
600            encrypted_buf[i] = self.table[buf[i] as usize];
601        }
602        self.inner.write(&encrypted_buf)
603    }
604
605    fn flush(&mut self) -> std::io::Result<()> {
606        self.inner.flush()
607    }
608}
609
610pub struct PazArcWriter<T: Write + Seek> {
611    writer: T,
612    headers: HashMap<String, PazEntry>,
613    encoding: Encoding,
614    is_audio: bool,
615    mov_key: Option<Vec<u8>>,
616    schema: Schema,
617    arc_key: ArcKey,
618    xor_key: u8,
619    compress: bool,
620    compress_level: u32,
621}
622
623impl<T: Write + Seek> PazArcWriter<T> {
624    pub fn new(
625        mut writer: T,
626        files: &[&str],
627        encoding: Encoding,
628        filename: &str,
629        config: &ExtraConfig,
630    ) -> Result<Self> {
631        let schema = config.musica_game_title.as_ref().ok_or_else(|| {
632            anyhow::anyhow!(
633                "Game title not specified. Use --musica-game-title to specify the game title."
634            )
635        })?;
636        let schema = query_paz_schema(schema).ok_or_else(|| {
637            anyhow::anyhow!("Unsupported game title '{}' for PAZ archive", schema)
638        })?;
639        let arc_name = std::path::Path::new(filename)
640            .file_stem()
641            .and_then(|s| s.to_str())
642            .ok_or_else(|| anyhow::anyhow!("Invalid filename"))?
643            .to_lowercase();
644        let is_audio = AUDIO_PAZ_NAMES.contains(&arc_name.as_str());
645        let is_video = arc_name == "mov";
646        let arc_key = schema.arc_keys.get(&arc_name).ok_or_else(|| {
647            anyhow::anyhow!(
648                "No ARC key found for archive name '{}' in game schema",
649                arc_name
650            )
651        })?;
652        let mov_key = if is_video {
653            let mut key = vec![0u8; 0x100];
654            for i in 0..0x100 {
655                key[i] = i as u8;
656            }
657            Some(key)
658        } else {
659            None
660        };
661        let start_offset = if schema.version > 0 { 0x20 } else { 0 };
662        if start_offset > 0 {
663            if schema.signature != 0 {
664                writer.write_u32(schema.signature)?;
665            }
666            writer.seek(SeekFrom::Start(start_offset))?;
667        }
668        let mut entries = HashMap::new();
669        for file in files {
670            let entry = PazEntry {
671                name: file.to_string(),
672                offset: 0,
673                unpacked_size: 0,
674                size: 0,
675                aligned_size: 0,
676                flags: 0,
677            };
678            entries.insert(file.to_string(), entry);
679        }
680        let xor_key = if let Some(xor_key) = config.musica_xor_key {
681            xor_key
682        } else {
683            schema.xor_key
684        };
685        if xor_key == 0 {
686            eprintln!(
687                "WARN: 0 xor key is used for PAZ archive. Output archive may broken. Use --musica-xor-key to specify a xor key. Xor key can be obtained from existing archive by unpacking it."
688            );
689            crate::COUNTER.inc_warning();
690        }
691        writer.write_u32(0)?; // Placeholder for index size
692        {
693            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&arc_key.index_key)?;
694            let stream = XoredStream::new(&mut writer, xor_key);
695            let mut index_stream = BlowfishEncryptor::new(blowfish, stream);
696            index_stream.write_u32(entries.len() as u32)?;
697            if let Some(mov_data) = &mov_key {
698                index_stream.write_all(mov_data)?;
699            }
700            for entry in entries.values() {
701                index_stream.write_struct(entry, false, encoding, &None)?;
702            }
703        }
704        let index_end = writer.stream_position()?;
705        let index_size = (index_end - start_offset - 4) as u32;
706        if xor_key != 0 {
707            let mut stream = XoredStream::new(&mut writer, xor_key);
708            stream.write_u32_at(start_offset, index_size)?;
709        } else {
710            writer.write_u32_at(start_offset, index_size)?;
711        };
712        Ok(PazArcWriter {
713            writer,
714            headers: entries,
715            encoding,
716            is_audio,
717            mov_key,
718            schema: schema.clone(),
719            arc_key: arc_key.clone(),
720            xor_key,
721            compress: config.musica_compress,
722            compress_level: config.zlib_compression_level,
723        })
724    }
725}
726
727impl<T: Write + Seek> Archive for PazArcWriter<T> {
728    fn new_file<'a>(
729        &'a mut self,
730        name: &str,
731        _size: Option<u64>,
732    ) -> Result<Box<dyn WriteSeek + 'a>> {
733        let entry = self
734            .headers
735            .get_mut(name)
736            .ok_or_else(|| anyhow::anyhow!("File '{}' not found in PAZ archive headers", name))?;
737        if entry.offset != 0 || entry.size != 0 {
738            return Err(anyhow::anyhow!(
739                "File '{}' already exists in PAZ archive",
740                name
741            ));
742        }
743        if let Some(data_key) = &self.arc_key.data_key {
744            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&data_key.bytes)?;
745            entry.offset = self.writer.stream_position()?;
746            let stream = XoredStream::new(&mut self.writer, self.xor_key);
747            let stream = BlowfishEncryptor::new(blowfish, stream);
748            let mut type_key = None;
749            entry.set_is_compressed(self.compress);
750            if self.schema.version > 0 && !self.compress {
751                if let Some(tkey) = self.schema.get_type_key(&entry, self.is_audio) {
752                    type_key = Some(tkey.to_string());
753                }
754            }
755            let writer = MemDataKeyWriter {
756                inner: Box::new(stream),
757                cache: MemWriter::new(),
758                type_key,
759                entry,
760                encoding: self.encoding,
761                version: self.schema.version,
762                compress: self.compress,
763                compress_level: self.compress_level,
764                compressed_size: 0,
765            };
766            return Ok(Box::new(writer));
767        } else if let Some(mov_key) = &self.mov_key {
768            entry.offset = self.writer.stream_position()?;
769            let stream = XoredStream::new(&mut self.writer, self.xor_key);
770            if self.schema.version < 1 {
771                let stream = TableEncryptedStream::new(stream, mov_key.clone())?;
772                let writer = MovDataWriter {
773                    inner: Box::new(stream),
774                    entry,
775                };
776                return Ok(Box::new(writer));
777            }
778            let type_key = self
779                .schema
780                .get_type_key(&entry, self.is_audio)
781                .ok_or_else(|| {
782                    anyhow::anyhow!("Data decryption key not found for entry '{}'.", entry.name)
783                })?;
784            let writer = MemMovDataKeyWriter {
785                inner: Box::new(stream),
786                cache: MemWriter::new(),
787                type_key: type_key.to_string(),
788                mov_key: mov_key.clone(),
789                entry,
790                encoding: self.encoding,
791            };
792            return Ok(Box::new(writer));
793        }
794        Err(anyhow::anyhow!("Data encryption key not found."))
795    }
796
797    fn new_file_non_seek<'a>(
798        &'a mut self,
799        name: &str,
800        size: Option<u64>,
801    ) -> Result<Box<dyn Write + 'a>> {
802        if let Some(data_key) = &self.arc_key.data_key {
803            let size = match size {
804                Some(size) => size,
805                None => {
806                    return Ok(Box::new(self.new_file(name, None)?));
807                }
808            };
809            let entry = self.headers.get_mut(name).ok_or_else(|| {
810                anyhow::anyhow!("File '{}' not found in PAZ archive headers", name)
811            })?;
812            if entry.offset != 0 || entry.size != 0 {
813                return Err(anyhow::anyhow!(
814                    "File '{}' already exists in PAZ archive",
815                    name
816                ));
817            }
818            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&data_key.bytes)?;
819            entry.offset = self.writer.stream_position()?;
820            let stream = XoredStream::new(&mut self.writer, self.xor_key);
821            let stream = BlowfishEncryptor::new(blowfish, stream);
822            if self.schema.version > 0 && !self.compress {
823                if let Some(tkey) = self.schema.get_type_key(&entry, self.is_audio) {
824                    let key = format!("{} {:08X} {}", entry.name.to_ascii_lowercase(), size, tkey);
825                    let key = encode_string(self.encoding, &key, false)?;
826                    let mut rc4 = Rc4::new(&key);
827                    if self.schema.version >= 2 {
828                        let crc = crc32fast::hash(&key);
829                        let skip = ((crc >> 12) as i32) & 0xFF;
830                        rc4.skip_bytes(skip as usize);
831                    }
832                    let writer = Rc4Stream::new(stream, rc4);
833                    let writer = DateKeyWriter {
834                        inner: Box::new(writer),
835                        entry,
836                    };
837                    return Ok(Box::new(writer));
838                }
839            }
840            if self.compress {
841                entry.set_is_compressed(true);
842                let writer = DataKeyComWriter::new(Box::new(stream), entry, self.compress_level);
843                return Ok(Box::new(writer));
844            }
845            let writer = DateKeyWriter {
846                inner: Box::new(stream),
847                entry,
848            };
849            return Ok(Box::new(writer));
850        } else if let Some(mov_key) = &self.mov_key {
851            let size = match size {
852                Some(size) => size,
853                None => {
854                    return Ok(Box::new(self.new_file(name, None)?));
855                }
856            };
857            let entry = self.headers.get_mut(name).ok_or_else(|| {
858                anyhow::anyhow!("File '{}' not found in PAZ archive headers", name)
859            })?;
860            if entry.offset != 0 || entry.size != 0 {
861                return Err(anyhow::anyhow!(
862                    "File '{}' already exists in PAZ archive",
863                    name
864                ));
865            }
866            entry.offset = self.writer.stream_position()?;
867            let stream = XoredStream::new(&mut self.writer, self.xor_key);
868            if self.schema.version < 1 {
869                let stream = TableEncryptedStream::new(stream, mov_key.clone())?;
870                let writer = MovDataWriter {
871                    inner: Box::new(stream),
872                    entry,
873                };
874                return Ok(Box::new(writer));
875            }
876            let type_key = self
877                .schema
878                .get_type_key(&entry, self.is_audio)
879                .ok_or_else(|| {
880                    anyhow::anyhow!("Data decryption key not found for entry '{}'.", entry.name)
881                })?;
882            let key = format!(
883                "{} {:08X} {}",
884                entry.name.to_ascii_lowercase(),
885                size,
886                type_key
887            );
888            let key = encode_string(self.encoding, &key, false)?;
889            let mut rkey = mov_key.clone();
890            let key_len = key.len();
891            for i in 0..0x100 {
892                rkey[i] ^= key[i % key_len];
893            }
894            let mut rc4 = Rc4::new(&rkey);
895            let key_block = rc4.generate_block((size as usize).min(0x10000));
896            let region = StreamRegion::new(stream, entry.offset, entry.offset + size)?;
897            let stream = XoredKeyStream::new(region, key_block, 0);
898            let writer = DateKeyWriter {
899                inner: Box::new(stream),
900                entry,
901            };
902            return Ok(Box::new(writer));
903        }
904        Err(anyhow::anyhow!("Data encryption key not found."))
905    }
906
907    fn write_header(&mut self) -> Result<()> {
908        let start_offset = if self.schema.version > 0 { 0x24 } else { 4 };
909        self.writer.seek(SeekFrom::Start(start_offset))?;
910        {
911            let blowfish: Blowfish<byteorder::LE> = Blowfish::new(&self.arc_key.index_key)?;
912            let stream = XoredStream::new(&mut self.writer, self.xor_key);
913            let mut index_stream = BlowfishEncryptor::new(blowfish, stream);
914            index_stream.write_u32(self.headers.len() as u32)?;
915            if let Some(mov_data) = &self.mov_key {
916                index_stream.write_all(mov_data)?;
917            }
918            for entry in self.headers.values() {
919                index_stream.write_struct(entry, false, self.encoding, &None)?;
920            }
921        }
922        Ok(())
923    }
924}
925
926struct DateKeyWriter<'a> {
927    inner: Box<dyn Write + 'a>,
928    entry: &'a mut PazEntry,
929}
930
931impl<'a> Write for DateKeyWriter<'a> {
932    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
933        let writed = self.inner.write(buf)?;
934        self.entry.size += writed as u32;
935        Ok(writed)
936    }
937
938    fn flush(&mut self) -> std::io::Result<()> {
939        self.inner.flush()
940    }
941}
942
943impl<'a> Drop for DateKeyWriter<'a> {
944    fn drop(&mut self) {
945        self.entry.unpacked_size = self.entry.size;
946        self.entry.aligned_size = (self.entry.size + 7) & !7;
947    }
948}
949
950struct DataKeyComWriter<'a> {
951    inner: ZlibEncoder<Box<dyn Write + 'a>>,
952    entry: &'a mut PazEntry,
953}
954
955impl<'a> DataKeyComWriter<'a> {
956    pub fn new(inner: Box<dyn Write + 'a>, entry: &'a mut PazEntry, level: u32) -> Self {
957        DataKeyComWriter {
958            inner: ZlibEncoder::new(inner, flate2::Compression::new(level)),
959            entry,
960        }
961    }
962}
963
964impl<'a> Write for DataKeyComWriter<'a> {
965    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
966        self.inner.write(buf)
967    }
968
969    fn flush(&mut self) -> std::io::Result<()> {
970        self.inner.flush()
971    }
972}
973
974impl<'a> Drop for DataKeyComWriter<'a> {
975    fn drop(&mut self) {
976        if let Err(e) = self.inner.try_finish() {
977            eprintln!(
978                "Error finishing compression for PAZ file entry '{}': {}",
979                self.entry.name, e
980            );
981            crate::COUNTER.inc_error();
982            return;
983        }
984        self.entry.size = self.inner.total_out() as u32;
985        self.entry.unpacked_size = self.inner.total_in() as u32;
986        self.entry.aligned_size = (self.entry.size + 7) & !7;
987    }
988}
989
990trait MyWriteSeek: Write + Seek {}
991impl<T: Write + Seek> MyWriteSeek for T {}
992
993struct MovDataWriter<'a> {
994    inner: Box<dyn MyWriteSeek + 'a>,
995    entry: &'a mut PazEntry,
996}
997
998impl<'a> Write for MovDataWriter<'a> {
999    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1000        self.inner.write(buf)
1001    }
1002
1003    fn flush(&mut self) -> std::io::Result<()> {
1004        self.inner.flush()
1005    }
1006}
1007
1008impl<'a> Seek for MovDataWriter<'a> {
1009    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
1010        self.inner.seek(pos)
1011    }
1012
1013    fn rewind(&mut self) -> std::io::Result<()> {
1014        self.inner.rewind()
1015    }
1016
1017    fn stream_position(&mut self) -> std::io::Result<u64> {
1018        self.inner.stream_position()
1019    }
1020}
1021
1022impl<'a> Drop for MovDataWriter<'a> {
1023    fn drop(&mut self) {
1024        if let Ok(pos) = self.inner.stream_position() {
1025            self.entry.unpacked_size = (pos - self.entry.offset) as u32;
1026            self.entry.size = self.entry.unpacked_size;
1027            self.entry.aligned_size = self.entry.size;
1028        } else {
1029            eprintln!(
1030                "Error getting stream position for PAZ file entry '{}'",
1031                self.entry.name
1032            );
1033            crate::COUNTER.inc_error();
1034        }
1035    }
1036}
1037
1038struct MemDataKeyWriter<'a> {
1039    inner: Box<dyn Write + 'a>,
1040    cache: MemWriter,
1041    type_key: Option<String>,
1042    entry: &'a mut PazEntry,
1043    encoding: Encoding,
1044    version: u32,
1045    compress: bool,
1046    compress_level: u32,
1047    compressed_size: u64,
1048}
1049
1050impl<'a> Write for MemDataKeyWriter<'a> {
1051    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1052        self.cache.write(buf)
1053    }
1054
1055    fn flush(&mut self) -> std::io::Result<()> {
1056        self.cache.flush()
1057    }
1058}
1059
1060impl<'a> Seek for MemDataKeyWriter<'a> {
1061    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
1062        self.cache.seek(pos)
1063    }
1064
1065    fn rewind(&mut self) -> std::io::Result<()> {
1066        self.cache.rewind()
1067    }
1068
1069    fn stream_position(&mut self) -> std::io::Result<u64> {
1070        self.cache.stream_position()
1071    }
1072}
1073
1074impl<'a> Drop for MemDataKeyWriter<'a> {
1075    fn drop(&mut self) {
1076        let data = &self.cache.data;
1077        self.entry.unpacked_size = data.len() as u32;
1078        self.entry.size = self.entry.unpacked_size;
1079        self.entry.aligned_size = (self.entry.size + 7) & !7;
1080        {
1081            let mut stream = if let Some(tkey) = &self.type_key {
1082                let key = format!(
1083                    "{} {:08X} {}",
1084                    self.entry.name.to_ascii_lowercase(),
1085                    self.entry.unpacked_size,
1086                    tkey
1087                );
1088                let key = match encode_string(self.encoding, &key, false) {
1089                    Ok(key) => key,
1090                    Err(e) => {
1091                        eprintln!(
1092                            "Error encoding key for PAZ file entry '{}': {}",
1093                            self.entry.name, e
1094                        );
1095                        crate::COUNTER.inc_error();
1096                        return;
1097                    }
1098                };
1099                let mut rc4 = Rc4::new(&key);
1100                if self.version >= 2 {
1101                    let crc = crc32fast::hash(&key);
1102                    let skip = ((crc >> 12) as i32) & 0xFF;
1103                    rc4.skip_bytes(skip as usize);
1104                }
1105                Box::new(Rc4Stream::new(&mut self.inner, rc4)) as Box<dyn Write>
1106            } else if self.compress {
1107                let stream = ZlibEncoder::new(
1108                    TrackStream::new(&mut self.inner, &mut self.compressed_size),
1109                    flate2::Compression::new(self.compress_level),
1110                );
1111                Box::new(stream) as Box<dyn Write>
1112            } else {
1113                Box::new(&mut self.inner) as Box<dyn Write>
1114            };
1115            if let Err(e) = stream.write_all(&data) {
1116                eprintln!("Error writing PAZ file entry '{}': {}", self.entry.name, e);
1117                crate::COUNTER.inc_error();
1118            }
1119        }
1120        if self.compress {
1121            self.entry.size = self.compressed_size as u32;
1122            self.entry.aligned_size = (self.entry.size + 7) & !7;
1123        }
1124    }
1125}
1126
1127struct MemMovDataKeyWriter<'a> {
1128    inner: Box<dyn MyWriteSeek + 'a>,
1129    cache: MemWriter,
1130    type_key: String,
1131    entry: &'a mut PazEntry,
1132    encoding: Encoding,
1133    mov_key: Vec<u8>,
1134}
1135
1136impl<'a> Write for MemMovDataKeyWriter<'a> {
1137    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
1138        self.cache.write(buf)
1139    }
1140
1141    fn flush(&mut self) -> std::io::Result<()> {
1142        self.cache.flush()
1143    }
1144}
1145
1146impl<'a> Seek for MemMovDataKeyWriter<'a> {
1147    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
1148        self.cache.seek(pos)
1149    }
1150
1151    fn rewind(&mut self) -> std::io::Result<()> {
1152        self.cache.rewind()
1153    }
1154
1155    fn stream_position(&mut self) -> std::io::Result<u64> {
1156        self.cache.stream_position()
1157    }
1158}
1159
1160impl<'a> Drop for MemMovDataKeyWriter<'a> {
1161    fn drop(&mut self) {
1162        let data = &self.cache.data;
1163        self.entry.unpacked_size = data.len() as u32;
1164        self.entry.size = self.entry.unpacked_size;
1165        self.entry.aligned_size = self.entry.size;
1166        let key = format!(
1167            "{} {:08X} {}",
1168            self.entry.name.to_ascii_lowercase(),
1169            self.entry.unpacked_size,
1170            self.type_key
1171        );
1172        let key = match encode_string(self.encoding, &key, false) {
1173            Ok(key) => key,
1174            Err(e) => {
1175                eprintln!(
1176                    "Error encoding key for PAZ file entry '{}': {}",
1177                    self.entry.name, e
1178                );
1179                crate::COUNTER.inc_error();
1180                return;
1181            }
1182        };
1183        let mut rkey = self.mov_key.clone();
1184        let key_len = key.len();
1185        for i in 0..0x100 {
1186            rkey[i] ^= key[i % key_len];
1187        }
1188        let mut rc4 = Rc4::new(&rkey);
1189        let key_block = rc4.generate_block(data.len().min(0x10000));
1190        let region = match StreamRegion::new(
1191            &mut self.inner,
1192            self.entry.offset,
1193            self.entry.offset + self.entry.size as u64,
1194        ) {
1195            Ok(region) => region,
1196            Err(e) => {
1197                eprintln!(
1198                    "Error creating stream region for PAZ file entry '{}': {}",
1199                    self.entry.name, e
1200                );
1201                crate::COUNTER.inc_error();
1202                return;
1203            }
1204        };
1205        let mut stream = XoredKeyStream::new(region, key_block, 0);
1206        if let Err(e) = stream.write_all(&data) {
1207            eprintln!("Error writing PAZ file entry '{}': {}", self.entry.name, e);
1208            crate::COUNTER.inc_error();
1209        }
1210    }
1211}
1212
1213#[test]
1214fn test_deserialize_paz() {
1215    for (game, schema) in PAZ_SCHEMA.iter() {
1216        println!("Game: {}", game);
1217        println!("Version: {}", schema.version);
1218        for (arc_name, arc_key) in schema.arc_keys.iter() {
1219            println!("  Arc Name: {}", arc_name);
1220            println!("    Index Key: {:02X?}", arc_key.index_key.bytes);
1221            if let Some(data_key) = &arc_key.data_key {
1222                println!("    Data Key: {:02X?}", data_key.bytes);
1223            } else {
1224                println!("    Data Key: None");
1225            }
1226        }
1227        for (type_name, type_key) in schema.type_keys.iter() {
1228            println!("  Type Name: {}, Type Key: {}", type_name, type_key);
1229        }
1230        println!("Signature: {:08X}", schema.signature);
1231        println!("XOR Key: {:02X}", schema.xor_key);
1232        if let Some(title) = &schema.title {
1233            println!("Game Title: {}", title);
1234        }
1235    }
1236}